package genetic.q4.v3;

import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import genetic.Genotype;
import genetic.Phenotype;
import genetic.q4.EternityPiece;

public class EternityPhenotype extends Phenotype{

	List<Integer> colors;
	List<Integer> extras;
	List<EternityPiece> forbidden;
	int fsize;
	int corner;

	public EternityPhenotype(List<Integer> colors, List<Integer> extras, List<EternityPiece> forbidden){
		this.colors = colors;
		this.extras = extras;
		this.forbidden = forbidden;
		this.fsize = colors.size();
		this.corner= colors.size()/4;
	}

	@Override
	public Genotype createRandomInstance(){
		List<EternityPiece> l2 = fillUpInnerFrame(new LinkedList<EternityPiece>());
		return new EternityGenotype(l2);
	}

	//	public void tmp(){
	//	List<Integer> colors2 = new LinkedList<Integer>();
	//	List<Integer> extras2 = new LinkedList<Integer>();
	//	extras2.add(l1.get(size-1).getColor(EternityPiece.GRAY, 2));
	//	for (int i=0; i<size; i++){
	//	if (i%corner==1 && i!=1){
	//	extras2.add(l1.get(i).getColor(EternityPiece.GRAY, 2));
	//	} else if (i%15!=0){
	//	colors2.add(l1.get(i).getColor(EternityPiece.GRAY, 2));
	//	}
	//	}
	//	}

	public List<EternityPiece> fillUpInnerFrame(List<EternityPiece> gen){
		List<EternityPiece> l;
		int color1,color2,color3=-9999999;

		int size = gen.size();

		if (size%corner == 1){
			color1 = colors.get(size);
			color2 = extras.get(size/corner);
		} else {
			int tmp = (size-1)-(size-1)%corner;
			color1 = colors.get(tmp);
			color2 = extras.get(tmp/corner);
			while (tmp<size-1){
				color1 = gen.get(tmp).getColor(color1, 1, color2, 2) ;
				color2 = colors.get(tmp+1);
				tmp++;
			}
		}
		boolean flag = true;
		for (int i=size; flag && i<fsize; i++){
			if (i%corner == 0){
				if (i!=0){
					EternityPiece p = gen.get(i-1);
					color3 = p.getColor(color1, 1, color2, 2);
					if (color3==-1){
						System.out.println(color1 + " - " + color2 + " - " + color3+","+i + " ");
						System.out.println(gen);
						System.out.println(gen.get(i-1));
					}
				}
				color1 = colors.get(i);
				color2 = extras.get(i/corner);
				if (i!=0){
					l = compundFilter(color3, 1, color1, 2, color2, gen);	//filter again to get corners only
				} else {
					l = filter(color1, 1, color2, gen);
				}
			} else {
				EternityPiece p = gen.get(i-1);
				int tmp = color1;
				color1 = p.getColor(color1, 1, color2, 2);	//TODO problem if it has 2 colors of the same kinds
				if (color1==-1){
					System.out.println(tmp + " - " + color2 + " ,"+i + " ");
					System.out.println(gen);
					System.out.println(gen.get(i-1));
				}
				color2 = colors.get(i);
				l = filter(color1, 1, color2, gen);
			}
			flag = l.size()>0;
			if (flag){
				int random = (int)(Math.random()*l.size());
				EternityPiece curPiece = l.remove(random);
				gen.add(curPiece);
			}


		}
		return gen;
	}

	public Genotype[] crossover(Genotype g1, Genotype g2){
		EternityGenotype eg1 = (EternityGenotype)(g1);
		EternityGenotype eg2 = (EternityGenotype)(g2);

		int color11 = eg1.getPiece(0).getColor(extras.get(0), 1, colors.get(0), 2);
		int color12 = eg2.getPiece(0).getColor(extras.get(0), 1, colors.get(0), 2);
		int color2 = colors.get(1);

		List<Integer> crossoverPoints = new LinkedList<Integer>();
		for (int i=1; i<Math.min(eg1.getSize()-1, eg2.getSize()-1); i++){
			//			System.out.print(color2 + "-" + color11 + ",\t");
			if (i%corner!=0){
				color11 = eg1.getPiece(i).getColor(color11, 1, color2, 2);
				color12 = eg2.getPiece(i).getColor(color12, 1, color2, 2);
			} else {
				color11 = eg1.getPiece(i).getColor(color11, 1, color2, 3);
				color12 = eg2.getPiece(i).getColor(color12, 1, color2, 3);
			}
			color2 = colors.get(i+1);
			if (color11==color12){
				crossoverPoints.add(i);
			}
		}
		//		System.out.println(color2 + "-" + color11);
		if (!crossoverPoints.isEmpty()){
			int cross = crossoverPoints.get((int)(Math.random()*crossoverPoints.size()));
			LinkedList<EternityPiece> kid1 = new LinkedList<EternityPiece>();
			LinkedList<EternityPiece> kid2 = new LinkedList<EternityPiece>();
			for (int i=0; i<=cross; i++){
				kid1.add(eg1.getPiece(i));
				kid2.add(eg2.getPiece(i));
			}
			boolean flag = true;
			for (int i=cross+1; flag && i < eg2.getSize(); i++){
				EternityPiece p = eg2.getPiece(i);
				if (kid1.contains(p)){
					flag = false;
				} else {
					kid1.add(p);
				}
			}
			fillUpInnerFrame(kid1);
			flag = true;
			for (int i=cross+1; flag && i < eg1.getSize(); i++){
				EternityPiece p = eg1.getPiece(i);
				if (kid2.contains(p)){
					flag = false;
				} else {
					kid2.add(p);
				}
			}
			fillUpInnerFrame(kid2);
//			System.out.println(crossoverPoints);
			return new Genotype[]{new EternityGenotype(kid1), new EternityGenotype(kid2)};
		} else {
			System.out.println("No crossover");
			return new Genotype[]{g1.clone(), g2.clone()};
		}



		//		List<EternityPiece> l1 = new LinkedList<EternityPiece>();
		//		List<EternityPiece> l2 = new LinkedList<EternityPiece>();
	}

	//	@Override
	//	public Genotype[] crossover(Genotype g1, Genotype g2) {
	//	EternityGenotype eg1 = (EternityGenotype)(g1);
	//	EternityGenotype eg2 = (EternityGenotype)(g2);

	//	List<EternityPiece> l1 = new LinkedList<EternityPiece>();
	//	List<EternityPiece> l2 = new LinkedList<EternityPiece>();

	//	//copy some of the first parent
	//	int rand = (int)(Math.random()*(eg1.getSize()-1))+1;
	//	for (int i=0; i<rand; i++){
	//	l1.add(eg1.getPiece(i));
	//	}
	//	rand = (int)(Math.random()*(eg2.getSize()-1))+1;
	//	for (int i=0; i<rand; i++){
	//	l2.add(eg2.getPiece(i));
	//	}
	//	//copy the rest using the second parent, or random if the piece is unavailable
	//	boolean flag = true;
	//	while(flag){
	////	int i = l1.size()-l1.size()%corner;
	////	int color1 = l1.get(i).getColor(extras.get(i/corner), 1, colors.get(i), 2) ;
	////	int color2 = colors.get(i+1);
	////	int j = l2.size()-l2.size()%corner;
	////	int color3 = l1.get(j).getColor(extras.get(j/corner), 1, colors.get(j), 2) ;
	////	int color4 = colors.get(j+1);
	//	i++;
	//	while(i<=l1.size()){
	//	color1 = l1.get(i).getColor(color1, 1, color2, 2);
	//	color2 = colors.get(i+1);
	//	i++;
	//	}
	//	int color = l1.get(l1.size()-1).getColor(l1, secondPlaceOffset)
	//	int index = eg2.indexOf(l1.get(l1.size()-1));
	//	boolean tmp = ((index+1) % corner == 0);
	//	boolean tmp2 = (l1.size() % corner == 0);
	//	if ((index>0) && (index<eg2.getOuterSize()-1) && ((tmp && tmp2) || (!tmp && !tmp2)) && !l1.contains(eg2.getPiece(0, index+1))){
	//	l1.add(eg2.getPiece(index+1));
	//	} else {
	//	flag = insertRandom(l1);
	//	}
	//	//			System.out.println(l1.toString());
	//	}
	//	l1 = fillUpInnerFrame(l1);
	//	l2 = fillUpInnerFrame(l2);
	//	return new Genotype[]{new EternityGenotype(l1, l12), new EternityGenotype(l2, l22)};
	//	}

	private boolean insertRandom(List<EternityPiece> l1){
		int size = l1.size();
		EternityPiece p = l1.get(size-1);
		int color;
		if (size%15==1){
			color = p.getColor(EternityPiece.GRAY, 1, EternityPiece.GRAY, 2);
		} else {
			color = p.getColor(EternityPiece.GRAY, 1);
		}
		List<EternityPiece> l = borderFilter(color, l1);
		if (size%15==0){
			l = filter(l, EternityPiece.GRAY, 3, EternityPiece.GRAY, l1);	//filter again to get corners only
		} else {
			l = filterOut(l, EternityPiece.GRAY, 3, EternityPiece.GRAY, l1);	//filter again to get corners only
		}
		boolean flag = l.size()>0;
		if (flag){
			int random = (int)(Math.random()*l.size());
			l1.add(l.remove(random));
		}
		return flag;
	}

	@Override
	public double fitness(Genotype g) {
		return ((EternityGenotype)g).getFitness();
	}

	@Override
	public List<Genotype> getInitialPopulation(int populationSize) {
		return getInitialRandomPopulation(populationSize);
	}

	@Override
	public List<Genotype> getMatingPool(List<Genotype> population, int populationSize) {
		return getMatingPoolRouletteWheel(population, populationSize);
	}

	@Override
	public Genotype mutate(Genotype g, double Pm) {
		if (Math.random()<Pm){
			EternityGenotype eg = (EternityGenotype)g;
			double third = (eg.getSize()/3.0);
			int rand = (int)(Math.random()*third+2*third);
			List<EternityPiece> newList1 = new LinkedList<EternityPiece>();
			for (int i=0; i<Math.min(rand,60); i++){
				newList1.add(eg.getPiece(i));
			}
			newList1 = fillUpInnerFrame(newList1);
			return new EternityGenotype(newList1);
		} else return g;
	}

	@Override
	public void updateFitness(List<Genotype> population) {
	}

	private List<EternityPiece> borderFilter(int requestedColor, List<EternityPiece> badList){
		return filter(EternityPiece.colormap.get(EternityPiece.GRAY), requestedColor, 1, EternityPiece.GRAY, badList);
	}

	private List<EternityPiece> filter(int requestedColor1, int offset, int requestedColor2, List<EternityPiece> badList){
		return filter(EternityPiece.colormap.get(requestedColor1), requestedColor1, offset, requestedColor2, badList);
	}

	private List<EternityPiece> compundFilter(int requestedColor1, int offset, int requestedColor2, int offset2, int requestedColor3, List<EternityPiece> badList){
		return filter(EternityPiece.colormap.get(requestedColor1), requestedColor1, offset, requestedColor2, offset2, requestedColor3, badList);
	}

	private List<EternityPiece> filter(List<EternityPiece> org, int baseColor, int offset, int requestedColor, int offset2,	int requestedColor2, List<EternityPiece> badList){
		Set<EternityPiece> ans = new HashSet<EternityPiece>();
		for (EternityPiece e: org){
			if (!badList.contains(e) && e.fits(baseColor, offset, requestedColor, offset2, requestedColor2)){
				ans.add(e);
			}
		}
		return new LinkedList<EternityPiece>(ans);
	}

	private List<EternityPiece> filter(List<EternityPiece> org, int baseColor, int offset, int requestedColor, List<EternityPiece> badList){
		Set<EternityPiece> ans = new HashSet<EternityPiece>();
		for (EternityPiece e: org){
			if (!badList.contains(e) && e.fits(baseColor, offset, requestedColor)){
				ans.add(e);
			}
		}
		return new LinkedList<EternityPiece>(ans);
	}

	private List<EternityPiece> filterOut(List<EternityPiece> org, int baseColor, int offset, int requestedColor, List<EternityPiece> badList){
		List<EternityPiece> ans = new LinkedList<EternityPiece>();
		for (EternityPiece e: org){
			if (!badList.contains(e) && !e.fits(baseColor, offset, requestedColor)){
				ans.add(e);
			}
		}
		return ans;
	}

	public static void main(String[] args) {
		List<Integer> gray1 = new LinkedList<Integer>();
		List<Integer> gray2 = new LinkedList<Integer>();
		int size = 60;
		for (int i=0; i<size; i++){
			gray1.add(EternityPiece.GRAY);
		}
		for (int i=0; i<4; i++){
			gray2.add(EternityPiece.GRAY);
		}
		int totalMax = 0;
		EternityPhenotype proto = new EternityPhenotype(gray1, gray2, new LinkedList<EternityPiece>());
		for (int i=0; i<2000; i++){
//			System.out.println(i);
			EternityGenotype p1 = (EternityGenotype)(proto.createRandomInstance());
//			EternityGenotype p2 = (EternityGenotype)(proto.createRandomInstance());
			//			p1 = (EternityGenotype)proto.mutate(p1, 1);
			//			p2 = (EternityGenotype)proto.mutate(p2, 1);
			//			if (p1.getFitness()==size-1 || p1.getSize()==size){ System.out.println(p1); System.out.println(proto.filter(EternityPiece.colormap.get(EternityPiece.GRAY), EternityPiece.GRAY, 0, EternityPiece.GRAY, p1.gen));}
			//			if (p2.getFitness()==size-1 || p2.getSize()==size){ System.out.println(p2); System.out.println(proto.filter(EternityPiece.colormap.get(EternityPiece.GRAY), EternityPiece.GRAY, 0, EternityPiece.GRAY, p2.gen));}
			//			int max = Math.max((int)p1.getFitness(), (int)p2.getFitness());
			//			totalMax = Math.max(max, totalMax);
//			System.out.println("XXXXXXXXX");
//			System.out.println(p1);
//			System.out.println(p2);
//			proto.crossover(p1, p2);
//			System.out.println("XXXXXXXXX");
			//			p = (EternityGenotype)(proto.mutate(p, 1));
			//			Genotype[] co = new EternityPhenotype().crossover(p1, p2);
			//			EternityGenotype p = (EternityGenotype)co[0];
						if (p1.getSize()==60){
						System.out.println(p1);
						System.out.println(i);
						}
			//			System.out.println(p.rep.size() + " - " + p.rep.toString());
			//			p = (EternityGenotype)co[1];
			//			System.out.println(p.rep.size() + " - " + p.rep.toString());
		}
		//		System.out.println(totalMax);
		//		System.out.println(proto.borderFilter(EternityPiece.GRAY, new LinkedList<EternityPiece>()));
	}

}
